#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <ctime>
#include <map>

#include <calcLib.h>
#include "protocol.h"

using namespace std;

#define MAXSIZE 200

int idCount = 0;                //记录有多少个客户端连接过，递增id
map<char *, int> clientmaps;    //从ip:port字符串到id的映射
time_t timelogs[1000];          //记录每个客户端id第一次与服务端通信的时间, 客户端超时未响应答案时移除任务
double anslogs[1000];           //记录每个客户端id被分配的任务答案，跟客户端返回的结果比较

int len;
int sock_fd;
int recv_num;                   //每次接收到的字节数大小
int send_num;                   //每次发出信息的字节数
char recv_buf[MAXSIZE];
struct sockaddr_in sockaddr_client;   //每次收到来自客户端的数据包时，存储客户端地址信息


/* 每秒检查任务列表 */
void checkclientlist(int signum) {
    time_t now = time(0);
    for (map<char *, int>::iterator it = clientmaps.begin(); it != clientmaps.end();) {  //每次调用遍历所有当前被分配任务但还未失效的客户端id
        if (now - timelogs[it->second] > 10) {   //如果当前时间距离客户端被分配任务的时间已经过去了十秒，则服务端会删除该任务
            printf("[Server] Info: client[%s] does not respond with answer in 10 seconds, so abort its job\n", it->first);
            clientmaps.erase(it++);
            continue;
        }
        it++;
    }
}

/* 设置定时器 */
void inititimer() {
    struct itimerval alarm;   //这个定时器是用来每秒比较是否有客户端超过10秒没返回答案
    alarm.it_interval.tv_sec = 1;
    alarm.it_interval.tv_usec = 0;
    alarm.it_value.tv_sec = 1;
    alarm.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL, &alarm, NULL);   //设置定时器
    signal(SIGALRM, checkclientlist);  //设置信号处理函数
}

/* 生成题目 */
struct calcProtocol generate(int id) {
    struct calcProtocol serverProto;  //存储服务端准备返回的计算题目
    int arithType = randomType() + 1;     //生成随机运算类型
    serverProto.arith = arithType;
    serverProto.id = id;

    if (arithType <= 4) {   //小于5意味着是整数运算
        int value1 = randomInt(), value2 = randomInt();
        serverProto.inValue1 = value1;
        serverProto.inValue2 = value2;
        if (arithType == 1) {
            anslogs[id] = value1 + value2;
        } else if (arithType == 2) {
            anslogs[id] = value1 - value2;
        } else if (arithType == 3) {
            anslogs[id] = value1 * value2;
        } else {
            anslogs[id] = value1 / value2;
        }
        printf("[Server] Info: going to send question \"%s %d %d\" to client\n", getRandomTypeName(arithType - 1),
               value1, value2);
    } else {      //大于4意味着浮点数运算
        double fvalue1 = randomFloat(), fvalue2 = randomFloat();
        serverProto.flValue1 = fvalue1;
        serverProto.flValue2 = fvalue2;
        if (arithType == 5) {
            anslogs[id] = fvalue1 + fvalue2;
        } else if (arithType == 6) {
            anslogs[id] = fvalue1 - fvalue2;
        } else if (arithType == 7) {
            anslogs[id] = fvalue1 * fvalue2;
        } else {
            anslogs[id] = fvalue1 / fvalue2;
        }
        printf("[Server] Info: going to send question \"%s %f %f\" to client\n", getRandomTypeName(arithType - 1),
               fvalue1, fvalue2);
    }
    timelogs[id] = time(0);    //记录任务开始时间，如果客户端未在10秒内返回结果，则终止任务
    return serverProto;
}

/* 处理信息 */
void handlemessage() {
    struct calcProtocol *clientData = (struct calcProtocol *) recv_buf;   //将接收到的信息强制转化为calcProtocol类型
    char client_addr[20];   //存储客户端的ip地址字符串
    inet_ntop(sockaddr_client.sin_family, &sockaddr_client.sin_addr, client_addr, sizeof(client_addr));
    char ip_and_port[30];   //存储ip+端口字符串作为唯一标识一个客户端程序的标识符
    sprintf(ip_and_port, "%s:%d", client_addr, sockaddr_client.sin_port);

    printf("--------------------from client[%s:%d]---------------------------\n", client_addr,
           sockaddr_client.sin_port);

    if (clientData->type == 22) {
        struct calcMessage *messa = (struct calcMessage *) recv_buf;
        printf("[Server] Info: client -> query for protocol support\n");
        if (messa->protocol != 17) {  //服务端只支持protocol数据域为17的协议，如果客户端不支持这个协议，则终止
            printf("[Server] Info: do not support the protocol proposed by client\n");
            struct calcMessage serverMessa;
            serverMessa.type = 2;
            serverMessa.message = 2;
            serverMessa.major_version = 1;
            serverMessa.minor_version = 0;
            send_num = sendto(sock_fd, &serverMessa, sizeof(serverMessa), 0, (struct sockaddr *) &sockaddr_client, len);
            if (send_num < 0) {
                perror("[Server] Error: send error");
                printf("[Server] Error: error: server can not send messages to client for unknown reason\n");
            }
            return;
        } else {
            printf("[Server] Info: support the protocol proposed by client\n");
            if (clientmaps.find(ip_and_port) == clientmaps.end()) {   //如果这是该客户端最近第一次访问服务端，则给他分配一个id标识符
                clientmaps[ip_and_port] = idCount;
                idCount++;
            }

            struct calcProtocol serverProto = generate(clientmaps[ip_and_port]);

            send_num = sendto(sock_fd, &serverProto, sizeof(serverProto), 0, (struct sockaddr *) &sockaddr_client, len);
            if (send_num < 0) {
                perror("[Server] Error: send error");
                printf("[Server] Error: server can not send messages to client for unknown reason\n");
                return;
            }
        }
    } else {  //到这里说明客户端返回的是calcProtocol类型，附带客户端的计算答案
        if (clientmaps.find(ip_and_port) == clientmaps.end()) {   //如果客户端的计算任务早已被服务端移除，则报错返回（可能因为传输过程中的延时导致到达时超过了10秒）
            printf("[Server] Info: client does not have a registerd id and there is no job for him to answer\n");
            return;
        }
        if (clientmaps[ip_and_port] != (int)clientData->id) {   //如果客户端没有使用服务端分配的id，则报错返回
            printf("[Server] Info: A client is attempting to use another id\n");
            return;
        }

        double clientAnswer;   //之所以可以用double类型来存储浮点或者整形，是因为整形转化为浮点型不会损失精度，所以没关系
        if (clientData->arith <= 4) {
            clientAnswer = clientData->inResult;
            printf("[Server] Info: client -> my answer to question \"%s %d %d\" is %d\n", getRandomTypeName(clientData->arith - 1),
                   clientData->inValue1, clientData->inValue2, clientData->inResult);
        } else {
            clientAnswer = clientData->flResult;
            printf("[Server] Info: client -> my answer to question \"%s %lf %lf\" is %lf\n", getRandomTypeName(clientData->arith - 1),
                   clientData->flValue1, clientData->flValue2, clientData->flResult);
        }

        struct calcMessage ansReply;
        ansReply.type = 4;    //4 代表此calcMessage是最后服务端发送给客户端的计算答案正确与否的回复信息，区分于之前的信息类别
        if (clientAnswer == anslogs[clientmaps[ip_and_port]]) {   //答案正确时
            ansReply.message = 1;
            printf("[Server] Info: OK, right answer\n");
        } else {
            ansReply.message = 2;
            printf("[Server] Info: NOT OK, wrong answer\n");
        }
        clientmaps.erase(ip_and_port);   //当验证完答案后移除计算任务和对应的客户端id，因为任务完成了，没有继续保留的意义了
        send_num = sendto(sock_fd, &ansReply, sizeof(ansReply), 0, (struct sockaddr *) &sockaddr_client, len);
        if (send_num < 0) {
            perror("[Server] Error: send error");
            printf("[Server] Error: server can not send messages to client for unknown reason\n");
            return;
        }
    }
}

int main(int argc, char *argv[]) {
    if (argc!=2) {
        printf("[Server] Error: argument number wrong. dir: %s argument num: (%d)\n",argv[0],argc);
        exit(1);
    }

    const char* delim=":";
    const char* server_ip=strtok(argv[1],delim);
    int server_port=atoi(strtok(NULL,delim));

    printf("[Server] Info: creating a socket\n");

    //创建服务端UDP套接字
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        perror("[Server] Error: socket creating error");
        printf("[Server] Error: server can not create a socket\n");
        exit(1);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip);
    len = sizeof(server_addr);

    printf("[Server] Info: binding the socket with local address\n");
    if (bind(sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
        perror("[Server] Error: socket bind address error");
        printf("[Server] Error: server can not bind the socket\n");
        exit(1);
    }

    initCalcLib();    //调用calclib初始化函数

    inititimer();     //初始化定时器

    printf("[Server] Info: ready to wait for messages from clients\n");

    while (1) {
        memset(&sockaddr_client, 0, sizeof(sockaddr_client));   //重置客户端地址信息结构体，复用
        recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *) &sockaddr_client,
                            (socklen_t * ) & len);
        if (recv_num < 0) {
            perror("[Server] Error: function recvfrom error");
            printf("[Server] Error: server failed in receiving messages from client\n");
            exit(1);
        }
        handlemessage();
    }

    close(sock_fd);
    printf("[Server] Info: program terminated\n");

    return (0);
}
